Bokeh is a visualization library that provides a simple but flexible declarative framework creating of web-based plots. Bokeh is developed primarily for Python but has a clean javascript API that makes it amenable to using with other programming languages. The rbokeh package provides a way to create Bokeh plots from R.

The work on this package is in the early stages. While the foundation and a lot of content is there, there are some major holes and much thought to be given to the overall design. See the very end of the document for more discussion.

To try it out, install the package and dependencies:

devtools::install_github("ramnathv/htmlwidgets")
devtools::install_github("hafen/rbokeh")
library(rbokeh)

Plots are constructed by initializing a figure() and then adding layers on top through various glyphs available in Bokeh, or abstractions of those glyphs that we have created for common use cases. The data input is typically x and y, and how they are specified is quite flexible (see examples below).

Some familiar R plots

Below are a few examples, many of which should be familiar to R users. More to come…

Note that in all examples, you don’t need to explicitly call plot() when you are doing this in your console. You just need to type the name of the figure and the print method will cause the plot to render. I had to call plot() explicitly in this document to get them to appear inline instead of in my browser.

points and lines and model fits

p <- figure(width = 600, height = 600) %>%
  lay_points(cars) %>%
  lay_lines(lowess(cars), line_color = "black")
p

z <- lm(dist ~ speed, data = cars)
p <- p %>% lay_abline(z, line_dash = 2)
p

abline

p <- figure() %>%
  lay_points(rnorm(100), rnorm(100), line_color = "gray") %>%
  lay_abline(0, 1, line_color = "black") %>%
  lay_abline(h = c(0, 1), line_color = "red") %>%
  lay_abline(v = 1, line_color = "blue")
p

image

p <- figure(xlim = c(0, 1), ylim = c(0, 1), title = "Volcano") %>%
  lay_image(volcano) %>%
  lay_contour(volcano)
p

Here is a Bokeh histogram function (using the quad glyph underneath).

hist and density

h <- figure(width = 600, height = 400) %>%
  lay_hist(faithful$eruptions, breaks = 40, freq = FALSE) %>%
  lay_density(faithful$eruptions)
h

boxplot

p <- figure() %>% lay_boxplot(rnorm(100))
p

p <- figure() %>% lay_boxplot(Sepal.Length, Species, data = iris)
p

curve

chippy <- function(x) sin(cos(x)*exp(-x/2))
p <- figure(width = 800) %>% 
  lay_curve(chippy, -8, 7, n = 2001)
p

segments

n <- 1000
p <- figure() %>%
  lay_segments(rnorm(n), rnorm(n), rnorm(n), rnorm(n), 
    line_alpha = 0.5, line_width = 2, line_color = gray(1:n / n))
p

quantile

## normal q-q with line
p <- figure() %>%
  lay_quantile(rnorm(1000), distn = qnorm) %>%
  lay_abline(0, 1, line_color = "black", line_width = 2)
p

## group by
p <- figure() %>%
  lay_quantile(Sepal.Length, group = Species, data = iris)
p

## only specific quantiles
p <- figure() %>%
  lay_quantile(Sepal.Length, group = Species, data = iris, 
    probs = c(0.25, 0.5, 0.75))
p

map

Very experimental and limited in functionality, but fun. The zoom tool is nice with this one. It would be nice to be able to specify text size relative to the overall scale, so the labels are small when zoomed out but increase in size when zoomed in.

require(maps)
data(us.cities)
cities <- subset(us.cities, long > -130)

p <- figure(width = 800, height = 550, padding_factor = 0) %>% 
  lay_map("county", fill_color = "#1F77B4", 
    line_color = "white", line_alpha = 0.2, fill_alpha = 0.5) %>%
  lay_map("state", line_color = "white") %>%
  lay_points(long, lat, data = cities, type = 19, size = 4, 
    fill_color = "black", fill_alpha = 0.75) %>%
  lay_text(long, lat, name, data = cities, text_font_size = "4pt")
p

Plotting characters

We have preserved things like pch in R, so that you can specify type to be like any of the pch values you are used to in R.

You can see the available options for type with the following:

point_types()

For the integer-valued types, the line and fill properties are taken care of for you. For the named types, you can control fill and line properties.

p <- figure() %>%
  lay_points(rnorm(10), rnorm(10), type = 0) %>%
  lay_points(rnorm(10), rnorm(10), type = 1) %>%
  lay_points(rnorm(10), rnorm(10), type = 2) %>%
  lay_points(rnorm(10), rnorm(10), type = 3) %>%
  lay_points(rnorm(10), rnorm(10), type = 4) %>%
  lay_points(rnorm(10), rnorm(10), type = "a")
p

More plotting characters (default for “filled” plotting characters is to fill with a less-saturated version of the line color):

p <- figure() %>%
  lay_points(rnorm(10), rnorm(10), type = 21) %>%
  lay_points(rnorm(10), rnorm(10), type = 22) %>%
  lay_points(rnorm(10), rnorm(10), type = 23) %>%
  lay_points(rnorm(10), rnorm(10), type = 24) %>%
  lay_points(rnorm(10), rnorm(10), type = 25)
p

Other options (alpha, size, single vector input)

p <- figure(height = 600, width = 600) %>%
  lay_points(rnorm(50), type = 21, fill_alpha = 0.3, size = 20) %>%
  lay_points(rnorm(50), type = 22, fill_alpha = 0.3, size = 30)
p

Lines

Similarly to pch mapping, we’ve also mapped the lty R options. The argument to use is line_dash.

set.seed(1234)
p <- figure() %>%
  lay_lines(rnorm(10), line_dash = 1, line_width = 2, line_alpha = 0.6) %>%
  lay_lines(rnorm(10), line_dash = 2, line_width = 2, line_alpha = 0.6) %>%
  lay_lines(rnorm(10), line_dash = 3, line_width = 2, line_alpha = 0.6) %>%
  lay_lines(rnorm(10), line_dash = 4, line_width = 2, line_alpha = 0.6) %>%
  lay_lines(rnorm(10), line_dash = 5, line_width = 2, line_alpha = 0.6) %>%
  lay_lines(rnorm(10), line_dash = 6, line_width = 2, line_alpha = 0.6)
p

Periodic table

A recreation of the beautiful periodic table found here. Note that there are some small things we still need to take care of (axis labels, remove grid lines, hover tool).

# prepare data
elements <- subset(elements, !is.na(group))
elements$group <- as.character(elements$group)
elements$period <- as.character(elements$period)

# add colors for groups
metals <- c("alkali metal", "alkaline earth metal", "halogen", 
  "metal", "metalloid", "noble gas", "nonmetal", "transition metal")
colors <- c("#a6cee3", "#1f78b4", "#fdbf6f", "#b2df8a", "#33a02c", 
  "#bbbb88", "#baa2a6", "#e08e79")
elements$color <- colors[match(elements$metal, metals)]

# make coordinates for labels
elements$symx <- paste(elements$group, ":0.1", sep = "")
elements$numbery <- paste(elements$period, ":0.8", sep = "")
elements$massy <- paste(elements$period, ":0.15", sep = "")
elements$namey <- paste(elements$period, ":0.3", sep = "")

# create figure
p <- figure(title = "Periodic Table", tools = "resize,hover", 
  ylim = as.character(c(7:1)), xlim = as.character(1:18),
  height = 600, width = 1200) %>%

# plot rectangles
lay_crect(group, period, data = elements, 0.9, 0.9,
  fill_color = color, line_color = color, fill_alpha = 0.6) %>%

# add symbol text
lay_text(symx, period, text = symbol, data = elements,
  text_font_style = "bold", text_font_size = "15pt", 
  text_align = "left", text_baseline = "middle") %>%

# add atomic number text
lay_text(symx, numbery, text = atomic.number, data = elements,
  text_font_size = "9pt", text_align = "left", text_baseline = "middle") %>%

# add name text
lay_text(symx, namey, text = name, data = elements,
  text_font_size = "6pt", text_align = "left", text_baseline = "middle") %>%

# add atomic mass text
lay_text(symx, massy, text = atomic.mass, data = elements,
  text_font_size = "6pt", text_align = "left", text_baseline = "middle")

p

Bokeh glyph examples

Below are several ‘tests’ to ensure that we can map to all of the glyphs available in Bokeh. See here for more detail on these glyphs.

We’ll look at markers first and then the remaining glyphs.

First to set up some data that will be reused a lot:

alpha <- c(6:2) / 10
colors <- c("pink", "orange", "red", "blue", "green")
xr <- yr <- c(0, 6)

Markers

A special type of glyph is a “marker”. This is analagous to the different “plotting characters” in R’s points() function. There is a points() method for rbokeh figures that makes these accessible

asterisk

a <- figure(xlim = xr, ylim = yr) %>%
  lay_points(x = 1:5, y = 5:1, type = "asterisk",
    line_color = colors, size = seq(12, 40, length = 5),
    line_width = c(2:6) / 2)
a

circle (no radius)

a <- figure(xlim = xr, ylim = yr) %>%
  lay_points(x = 1:5, y = 5:1, type = "circle",
    size = seq(12, 40, length = 5),
    line_color = colors, fill_color = colors, fill_alpha = 0.5)
a

circle_cross

a <- figure(xlim = xr, ylim = yr) %>%
  lay_points(x = 1:5, y = 5:1, type = "circle_cross",
    size = seq(12, 40, length = 5),
    line_color = colors, fill_color = colors, fill_alpha = 0.5)
a

circle_x

a <- figure(xlim = xr, ylim = yr) %>%
  lay_points(x = 1:5, y = 5:1, type = "circle_x",
    size = seq(12, 40, length = 5),
    line_color = colors, fill_color = colors, fill_alpha = 0.5)
a

cross

a <- figure(xlim = xr, ylim = yr) %>%
  lay_points(x = 1:5, y = 5:1, type = "cross",
    size = seq(12, 40, length = 5),
    line_width = 3, line_color = colors)
a

diamond

a <- figure(xlim = xr, ylim = yr) %>%
  lay_points(x = 1:5, y = 5:1, type = "diamond",
    size = seq(12, 40, length = 5),
    line_color = colors, fill_color = colors, fill_alpha = 0.5)
a

diamond_cross

a <- figure(xlim = xr, ylim = yr) %>%
  lay_points(x = 1:5, y = 5:1, type = "diamond_cross",
    size = seq(12, 40, length = 5),
    line_color = colors, fill_color = colors, fill_alpha = 0.5)
a

inverted_triangle

a <- figure(xlim = xr, ylim = yr) %>%
  lay_points(x = 1:5, y = 5:1, type = "inverted_triangle",
    size = seq(12, 40, length = 5),
    line_color = colors, fill_color = colors, fill_alpha = 0.5)
a

square

a <- figure(xlim = xr, ylim = yr) %>%
  lay_points(x = 1:5, y = 5:1, type = "square",
    size = seq(12, 40, length = 5),
    line_color = colors, fill_color = colors, fill_alpha = 0.5)
a

square_cross

a <- figure(xlim = xr, ylim = yr) %>%
  lay_points(x = 1:5, y = 5:1, type = "square_cross",
    size = seq(12, 40, length = 5),
    line_color = colors,
    fill_color = colors, fill_alpha = 0.5)
a

square_x

a <- figure(xlim = xr, ylim = yr) %>%
  lay_points(x = 1:5, y = 5:1, type = "square_x",
    size = seq(12, 40, length = 5),
    line_color = colors, fill_color = colors, fill_alpha = 0.5)
a

triangle

a <- figure(xlim = xr, ylim = yr) %>%
  lay_points(x = 1:5, y = 5:1, type = "triangle",
    size = seq(12, 40, length = 5),
    line_color = colors, fill_color = colors, fill_alpha = 0.5)
a

x

a <- figure(xlim = xr, ylim = yr) %>%
  lay_points(x = 1:5, y = 5:1, type = "x",
    size = seq(12, 40, length = 5),
    line_color = colors, line_width = c(2:6) / 2)
a

Glyphs

Here are all the non-marker Bokeh glyphs.

annular_wedge

p <- figure(xlim = xr, ylim = yr) %>%
  lay_annular_wedge(x = 1:5, y = 5:1, 
    start_angle = 0, end_angle = c(1:5) / 2,
    inner_radius = 0.3, outer_radius = 0.7,
    fill_color = colors, fill_alpha = alpha, 
    line_color = colors)
p

annulus

p <- figure(xlim = xr, ylim = yr) %>%
  lay_annulus(x = 1:5, y = 5:1, 
    inner_radius = c(2:6) / 10, 
    outer_radius = 0.8,
    fill_color = colors, line_color = colors, 
    fill_alpha = alpha)
p

arc

p <- figure(xlim = xr, ylim = yr) %>%
  lay_arc(x = 1:5, y = 5:1, 
    start_angle = 0, end_angle = c(1.5, 2, 2.5, 3.0, 4.5),
    line_color = colors, line_width = 1:5, radius = 0.5)
p

bezier

p <- figure(xlim = xr, ylim = yr) %>%
  lay_bezier(x0 = 1:5, x1 = 2:6, y0 = 5:1, y1 = 5:1, 
    cx0 = 1:5, cy0 = rep(6, 5), cx1 = 4, cy1 = rep(6, 5), 
    line_width = 2, line_color = colors)
p

image

N <- 300
d1 <- rep(1:N, each = N)
d2 <- rep(1:N, times = N)
d <- sin(10*d1/N)*cos(20*d2/N)
p <- figure(xlim = c(0, 10), ylim = c(0, 10)) %>%
  lay_image(x = 0, y = 0, dw = 10, dh = 10,
    image = d, rows = N, cols = N, palette = "Spectral-10")
p

image_rgba

not working

image_url

not working

line

p <- figure(xlim = xr, ylim = yr) %>%
  lay_line(x = 1:5, y = c(4, 5, 3, 5.5, 1), 
  line_color = "#43A2CA",
  line_dash = c(5, 2), line_width = 2)
p

multi_line

p <- figure(xlim = xr, ylim = yr) %>%
  lay_multi_line(xs = list(c(1, 2, 3), c(2, 3, 4), 
      c(0, 1, 0), c(2, 5, 4), c(5, 3, 2)),
    ys = list(c(5, 2, 3), c(1, 1, 4), 
      c(5, 5, 2), c(4, 1, 5), c(3, 5, 5)),
    line_width = c(1, 1.5, 2, 2.5, 3),
    line_color = colors,
    line_dash = c(5, 2))
p

oval

p <- figure(xlim = xr, ylim = yr) %>%
  lay_oval(x = 1:5, y = 5:1,
    angle = c(1:5) / 5, width = c(1:5) / 5,
    height = c(5:1) / 5, line_color = colors,
    fill_color = colors, fill_alpha = alpha)
p

patch

Renamed to polygon to preserve the expected behavior of polygon in R.

p <- figure(xlim = xr, ylim = yr) %>%
  lay_polygon(x = 1:5, y = c(4, 5, 3, 5.5, 1), 
    line_width = 2, line_dash = c(5, 2, 5, 6),
    line_color = "#2C7FB8", fill_color = "#7FCDBB")
p

patches

Renamed to polygons to preserve the expected behavior of polygons in R.

p <- figure(xlim = xr, ylim = yr) %>%
  lay_polygons(xs = list(c(1, 2, 3), c(2, 3, 4), 
      c(0, 1, 0), c(2, 5, 4), c(5, 3, 2)),
    ys = list(c(5, 2, 3), c(1, 1, 4), c(5, 5, 2), 
      c(4, 1, 5), c(3, 5, 4)),
    line_width = 2, line_dash = c(5, 2),
    fill_alpha = alpha, fill_color = colors,
    line_color = colors)
p

quad

Renamed to rect to preserve the expected behavior of rect in R.

p <- figure(xlim = xr, ylim = yr) %>%
  lay_rect(ytop = c(5.1, 4.2, 3.7, 2.4, 1.5),
    ybottom = c(4.9, 3.8, 2.3, 1.6, 0.5),
    xleft = c(0.9, 1.6, 2.7, 3.6, 4.4),
    xright = c(1.1, 2.4, 3.3, 4.4, 5.7),
    fill_alpha = alpha, fill_color = colors,
    line_color = colors)
p

quadratic

p <- figure(xlim = xr, ylim = yr) %>%
  lay_quadratic(x0 = 1:5, x1 = 2:6, y0 = 5:1, y1 = 5:1, 
    cx = 1:5, cy = rep(6, 5), 
    line_width = 2, line_color = colors)
p

ray

p <- figure(xlim = xr, ylim = yr) %>%
  lay_ray(x = 1:5, y = 5:1,
    angle = c(1:5) / 2, length = 30,
    line_width = 2, line_color = colors)
p

rect

Renamed to crect for “centered” rect to preserve the expected behavior of rect in R.

p <- figure(xlim = xr, ylim = yr) %>%
  lay_crect(x = 1:5, y = 5:1,
    angle = c(1:5) / 5, 
    width = c(1:5) / 5, height = c(5:1) / 5,
    fill_alpha = alpha, 
    line_color = colors, fill_color = colors)
p

segment

p <- figure(xlim = xr, ylim = yr) %>%
  lay_segments(x0 = 1:5, x1 = 2:6, y0 = 5:1, 
    y1 = c(6, 4.8, 3.6, 2.4, 1.2), 
    line_width = 2, line_color = colors)
p

text

p <- figure(xlim = xr, ylim = yr) %>%
  lay_text(x = 1:5, y = 5:1, 
    text = c("foo", "bar", "baz", "hello", "world"),
    angle = c(1:5) / 10,
    text_color = colors)
p

wedge

p <- figure(xlim = xr, ylim = yr) %>%
  lay_wedge(x = 1:5, y = 5:1, 
    start_angle = 0, end_angle = c(1:5) / 2,
    radius = 0.7,
    fill_color = colors, fill_alpha = alpha, 
    line_color = colors)
p

Notes on development / design

The current design should look familiar in some ways to those who are acquainted with R base plots, ggplot2, or ggvis. The original goal was to put the basic mappings from R to Bokeh in place and provide a mechanism to add components to a plot layer by layer. Once that was done, it was quite easy to build on top of Bokeh glyphs so I started to create some higher-level layer functions.

Building a figure

The way things work right now is that a figure is initialized and then layers are added. Initially the implementation was using reference classes, but we decided to switch to piping with matrittr for simplicity. After doing so, the syntax started looking more similar to ggvis.

For each layer, one must specify:

  • the desired “layer” function, prefixed with lay_
  • the data attributes required for that layer, either given directly or as references to columns in a data frame
  • applicable aesthetic attributes:
  • line_color, line_width, line_alpha, line_join, line_cap, line_dash, line_dash_offset
  • fill_color, fill_alpha
  • text_font, text_font_size, text_font_style, text_color, text_alpha, text_align, text_baseline
  • Not all attributes are applicable to all glyphs, and there are additional attributes such as size, angle, etc. for some glyphs.
  • a groups argument is available for some layers, specifying a variable against which to group the data prior to computing whatever artifacts will be visualized. Note that this is meant to be separate from a grouping mechanism for applying aesthetics differently to different pieces of data in the layer. How to do this best still needs to be sorted out.

Axes

Axes can be numeric or categorical. These can be mixed. And most glyphs support either type. Layers in a figure must consistently provide data that conforms to the scales that have been determined for each axis.

Axis limits can be provided explicitly in the call to figure() using the xlim and ylim arguments. For categorical axes, this is simply a vector of the unique values, in the order you would like to see them on the axis. However, if no limits are supplied, limit information for each layer is stored and prior to plotting, the plot region for all points is calculated.

Deferred layer computation

Some layers require computation to be deferred until the figure is being plotted. For example, lay_abline() needs to know the final extend of both axes so it only plots inside the area for the figure.

Design issues

The most pressing thing to consider from a design point of view is, for those familiar with the grammar of graphics, to map aesthetics to the data in each layer. Currently there is a very simple “theme” mechanism that cycles through a palette of colors (tableau10) for each new component added to a figure. This is just something to start with so the defaults don’t look terrible, but a lot of thought / work needs to go into the proper default way to specify and apply attributes to the points being plotted.

Plotting components

Basic elements:

  • lay_points
  • lay_lines
  • lay_segments
  • lay_rect
  • lay_polygon
  • lay_polygons
  • lay_abline
  • lay_image
  • lay_text

Derived from basic elements:

  • lay_curve
  • lay_map
  • lay_hist
  • lay_density
  • lay_boxplot
  • lay_quantile
  • lay_contour

Additional Bokeh glyphs:

  • lay_annular_wedge
  • lay_annulus
  • lay_arc
  • lay_bezier
  • lay_crect
  • lay_line
  • lay_multi_line
  • lay_oval
  • lay_quadratic
  • lay_ray
  • lay_wedge
  • lay_image_rgba
  • lay_image_url